{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Unit Root Testing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_This setup code is required to run in an IPython notebook_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn\n",
    "\n",
    "warnings.simplefilter(\"ignore\")\n",
    "\n",
    "seaborn.set_style(\"darkgrid\")\n",
    "plt.rc(\"figure\", figsize=(16, 6))\n",
    "plt.rc(\"savefig\", dpi=90)\n",
    "plt.rc(\"font\", family=\"sans-serif\")\n",
    "plt.rc(\"font\", size=14)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Most examples will make use of the Default premium, which is the difference between the yields of BAA and AAA rated corporate bonds. The data is downloaded from FRED using pandas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import statsmodels.api as sm\n",
    "\n",
    "import arch.data.default\n",
    "\n",
    "default_data = arch.data.default.load()\n",
    "default = default_data.BAA.copy()\n",
    "default.name = \"default\"\n",
    "default = default - default_data.AAA.values\n",
    "fig = default.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Default premium is clearly highly persistent.  A simple check of the autocorrelations confirms this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "acf = pd.DataFrame(sm.tsa.stattools.acf(default), columns=[\"ACF\"])\n",
    "fig = acf[1:].plot(kind=\"bar\", title=\"Autocorrelations\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Augmented Dickey-Fuller Testing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Augmented Dickey-Fuller test is the most common unit root test used.  It is a regression of the first difference of the variable  on its lagged level as well as additional lags of the first difference.  The null is that the series contains a unit root, and the (one-sided) alternative is that the series is stationary. \n",
    "\n",
    "By default, the number of lags is selected by minimizing the AIC across a range of lag lengths (which can be set using `max_lag` when initializing the model).  Additionally, the basic test includes a constant in the ADF regression.\n",
    "\n",
    "These results indicate that the Default premium is stationary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from arch.unitroot import ADF\n",
    "\n",
    "adf = ADF(default)\n",
    "print(adf.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The number of lags can be directly set using `lags`.  Changing the number of lags makes no difference to the conclusion.\n",
    "\n",
    "**Note**: The ADF assumes residuals are white noise, and that the number of lags is sufficient to pick up any dependence in the data."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Setting the number of lags"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "adf = ADF(default, lags=5)\n",
    "print(adf.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Deterministic terms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The deterministic terms can be altered using `trend`.  The options are:\n",
    "\n",
    "* `'nc'` : No deterministic terms\n",
    "* `'c'` : Constant only\n",
    "* `'ct'` : Constant and time trend\n",
    "* `'ctt'` : Constant, time trend and time-trend squared\n",
    "\n",
    "Changing the type of constant also makes no difference for this data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "adf = ADF(default, trend=\"ct\", lags=5)\n",
    "print(adf.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Regression output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ADF uses a standard regression when computing results.  These can be accesses using `regression`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "reg_res = adf.regression\n",
    "print(reg_res.summary().as_text())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "\n",
    "resids = pd.DataFrame(reg_res.resid)\n",
    "resids.index = default.index[6:]\n",
    "resids.columns = [\"resids\"]\n",
    "fig = resids.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since the number lags was directly set, it is good to check whether the residuals appear to be white noise."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "acf = pd.DataFrame(sm.tsa.stattools.acf(reg_res.resid), columns=[\"ACF\"])\n",
    "fig = acf[1:].plot(kind=\"bar\", title=\"Residual Autocorrelations\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dickey-Fuller GLS Testing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Dickey-Fuller GLS test is an improved version of the ADF which uses a GLS-detrending regression before running an ADF regression with no additional deterministic terms.  This test is only available with a constant or constant and time trend (`trend='c'` or  `trend='ct'`).\n",
    "\n",
    "The results of this test agree with the ADF results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from arch.unitroot import DFGLS\n",
    "\n",
    "dfgls = DFGLS(default)\n",
    "print(dfgls.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The trend can be altered using `trend`.  The conclusion is the same. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dfgls = DFGLS(default, trend=\"ct\")\n",
    "print(dfgls.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Phillips-Perron Testing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Phillips-Perron test is similar to the ADF except that the regression run does not include lagged values of the first differences.  Instead, the PP test fixed the t-statistic using a long run variance estimation, implemented using a Newey-West covariance estimator.  \n",
    "\n",
    "By default, the number of lags is automatically set, although this can be overridden using `lags`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from arch.unitroot import PhillipsPerron\n",
    "\n",
    "pp = PhillipsPerron(default)\n",
    "print(pp.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is important that the number of lags is sufficient to pick up any dependence in the data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pp = PhillipsPerron(default, lags=12)\n",
    "print(pp.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The trend can be changed as well."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pp = PhillipsPerron(default, trend=\"ct\", lags=12)\n",
    "print(pp.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, the PP testing framework includes two types of tests. One which uses an ADF-type regression of the first difference on the level, the other which regresses the level on the level.  The default is the `tau` test, which is similar to an ADF regression, although this can be changed using `test_type='rho'`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pp = PhillipsPerron(default, test_type=\"rho\", trend=\"ct\", lags=12)\n",
    "print(pp.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## KPSS Testing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The KPSS test differs from the three previous in that the null is a stationary process and the alternative is a unit root.  \n",
    "\n",
    "Note that here the null is rejected which indicates that the series might be a unit root."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from arch.unitroot import KPSS\n",
    "\n",
    "kpss = KPSS(default)\n",
    "print(kpss.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Changing the trend does not alter the conclusion."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "kpss = KPSS(default, trend=\"ct\")\n",
    "print(kpss.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Zivot-Andrews Test\n",
    "\n",
    "The Zivot-Andrews test allows the possibility of a single structural break in the series. Here we test the default using the test."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from arch.unitroot import ZivotAndrews\n",
    "\n",
    "za = ZivotAndrews(default)\n",
    "print(za.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Variance Ratio Testing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Variance ratio tests are not usually used as unit root tests, and are instead used for testing whether a financial return series is a pure random walk versus having some predictability.  This example uses the excess return on the market from Ken French's data. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "import arch.data.frenchdata\n",
    "\n",
    "ff = arch.data.frenchdata.load()\n",
    "excess_market = ff.iloc[:, 0]  # Excess Market\n",
    "print(ff.describe())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The variance ratio compares the variance of a 1-period return to that of a multi-period return. The comparison length has to be set when initializing the test.  \n",
    "\n",
    "This example compares 1-month to 12-month returns, and the null that the series is a pure random walk is rejected. Negative values indicate some positive autocorrelation in the returns (momentum)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from arch.unitroot import VarianceRatio\n",
    "\n",
    "vr = VarianceRatio(excess_market, 12)\n",
    "print(vr.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By default the VR test uses all overlapping blocks to estimate the variance of the long period's return.  This can be changed by setting  `overlap=False`.  This lowers the power but does not change the conclusion."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "warnings.simplefilter(\"always\")  # Restore warnings\n",
    "\n",
    "vr = VarianceRatio(excess_market, 12, overlap=False)\n",
    "print(vr.summary().as_text())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Note**: The warning is intentional. It appears here since when it is not possible to use all data since the data length is not an integer multiple of the long period when using non-overlapping blocks.  There is little reason to use `overlap=False`."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
